#include <iostream>
#include "interpreter.h"
#include "sdk.h"
#include "misc.h"

using namespace std;

namespace SDK{

#define BUFFNAMES(Action)\
    Action(ScourgeBuff);\
    Action(OverloadHighBuff);\
    Action(OverloadLowBuff);\
    Action(ShieldingBuff);\
    Action(DecAttackBuff);\
    Action(AirborneSurge)
    UnitTypeNamespace::BuffType loadBuffName(string name){
#define LOADBUFFNAMEIF(bName) if (name == #bName) return UnitTypeNamespace::BuffType::bName;
        BUFFNAMES(LOADBUFFNAMEIF);
#undef LOADBUFFNAMEIF
        throw invalid_argument(name.c_str());
    }
    string writeBuffName(UnitTypeNamespace::BuffType buffType){
#define WRITEBUFFNAMEIF(bName) if (buffType == UnitTypeNamespace::BuffType::bName) return #bName;
        BUFFNAMES(WRITEBUFFNAMEIF);
#undef WRITEBUFFNAMEIF
        throw invalid_argument("");
    }
#undef BUFFNAMES

#define SKILLNAMES(Action)\
    Action(PlainAttack);\
    Action(Split);\
    Action(Recover);\
    Action(Shielding);\
    Action(Airborne);\
    Action(Overload);\
    Action(Scourge);\
    Action(Bombing);\
    Action(Evolve);\
    Action(Vestigial);\
    Action(Connect);\
    Action(Disconnect);\
    Action(Transfer);
    UnitTypeNamespace::SkillType loadSkillName(string name){
#define LOADSKILLNAMEIF(sName) if (name == #sName) return UnitTypeNamespace::SkillType::sName##Skill;
        SKILLNAMES(LOADSKILLNAMEIF);
#undef LOADSKILLNAMEIF
        throw invalid_argument(name.c_str());
    }
    string writeSkillName(UnitTypeNamespace::SkillType skillType){
#define WRITESKILLNAMEIF(sName) if (skillType == UnitTypeNamespace::SkillType::sName##Skill) return #sName;
        SKILLNAMES(WRITESKILLNAMEIF);
#undef WRITESKILLNAMEIF
        throw invalid_argument("");
    }

#undef SKILLNAMES

    PPlayerInfo::Robot::Buff interpretBuff(string name, Json::Value root){
        PPlayerInfo::Robot::Buff buff;
        buff.LeftTime = root["remaining"].asInt();
        buff.type = loadBuffName(name);
        return buff;
    }
    PPlayerInfo::Robot::Skill interpretSkill(string name, Json::Value root){
        PPlayerInfo::Robot::Skill skill;
        if (!root["cd"].isNull()) {
            skill.cd = root["cd_timer"].asInt();
            skill.max_cd = root["cd"].asInt();
            skill.type = loadSkillName(name);
        }
        return skill;
    }
    PPlayerInfo::Robot interpretRobot(Json::Value root){
        PPlayerInfo::Robot robot;
        robot.id = root["id"].asString();
        int x, y, z;
        sscanf(root["pos"].asString().c_str(), "(%d, %d, %d)", &x, &y, &z);
        robot.pos = Pos(x, y);
        robot.type = RobotType(stoi(root["properties"]["type"].asString()));
#define LOADINTDATA(pName) robot.pName = stoi(root["properties"][#pName].asString());
        LOADINTDATA(team);
        LOADINTDATA(attack);
        LOADINTDATA(attack_range);
        LOADINTDATA(hp);
        LOADINTDATA(max_hp);
        LOADINTDATA(level);
        LOADINTDATA(defense);
        LOADINTDATA(energy);
        LOADINTDATA(consumption);
        LOADINTDATA(efficiency);
        LOADINTDATA(transport_capacity);
        LOADINTDATA(transport_range);
        LOADINTDATA(outdeg);
#undef LOADINTDATA
        vector<string> pNames = root["skillList"].getMemberNames();
        for (int i = 0; i < pNames.size(); ++ i){
            string name = pNames[i];
            if (!root["skillList"][name]["remaining"].isNull()){
                if (name.find("LinktimeBuff") == string::npos)
                    robot.buffs.push_back(interpretBuff(name, root["skillList"][name]));
            }
            else {
                robot.skills.push_back(interpretSkill(name, root["skillList"][name]));
            }
        }
        return robot;
    }
    PPlayerInfo::Event interpretEvent(Json::Value root){
        PPlayerInfo::Event event;
        if (root["action"].asString() == "Link"){
            event.event = LinkEvent;
            event.sender = root["sender"].asString();
            event.target = root["params"][1].asString();
        }
        else if (root["action"].asString() == "TriggeredDeath"){
            event.event = TriggeredDeathEvent;
            event.sender = root["sender"].asString();
        }
        else if (root["action"].asString() == "SpreadScourge"){
            event.event = SpreadScourgeEvent;
            event.target = root["params"][0].asString();
        }
        else {
            event.event = SkillCallEvent;
            event.sender = root["sender"].asString();
            event.skillType = loadSkillName(root["action"].asString());
            string paramDescription = ParamsDescription[int(event.skillType)];
            int paramId = 0;
            Json::Value &params = root["params"];
            while (paramDescription.find("<") != string::npos) {
                string paramData = paramDescription.substr(paramDescription.find("<") + 1,
                                                           paramDescription.find(">") - paramDescription.find("<") - 1);
                if (paramData == "id") {
                    event.target = params[paramId].asString();
                } else if (paramData == "x") {
                    event.pos.x = stoi(params[paramId].asString());
                } else if (paramData == "y") {
                    event.pos.y = stoi(params[paramId].asString());
                } else if (paramData == "energy") {
                    event.energy = stoi(params[paramId].asString());
                } else if (paramData == "type") {
                    if (stoi(params[paramId].asString()) == -1){
                        event.RemainSame = true;
                    }
                    else {
                        event.RemainSame = false;
                        event.targetType = RobotType(stoi(params[paramId].asString()));
                    }
                }
                paramDescription = paramDescription.substr(paramDescription.find(">") + 1);
                ++ paramId;
            }
        }
        return event;
    }

    PPlayerInfo interpretPlayerInfoStatus(Json::Value root){
        PPlayerInfo playerInfo;
        playerInfo.round = root["round"].asInt();
        playerInfo.team = root["playerId"].asInt();
        srand(root["rand_seed"].asInt());
        for (int i = 0; i < root["actions"].size(); ++ i){
            playerInfo.events.push_back(interpretEvent(root["actions"][i]));
        }
        for (int i = 0; i < root["objects"].size(); ++ i) {
            Json::Value &robot = root["objects"][i];
            playerInfo.robots.push_back(interpretRobot(robot));
        }

        for (int i = 0; i < root["objects"].size(); ++ i) {
            Json::Value &robot = root["objects"][i];
            string id1 = robot["id"].asString();
            vector<string> pNames = robot["skillList"].getMemberNames();
            vector<PPlayerInfo::Edge> my_edges;
            if (robot["properties"]["link_out"].asString() != "|"){
                vector<string> outSet;
                SplitString(robot["properties"]["link_out"].asString(), outSet, "|");
                //cerr << "link_out " << robot["properties"]["link_out"].asString() << endl;
                for (int j = 0; j < outSet.size(); ++ j)
                    if (outSet[j].size() > 0){
                        string id2 = outSet[j];
                        PPlayerInfo::Edge edge;
                        edge.source = playerInfo.RobotNamed(id1)->pos;
                        edge.target = playerInfo.RobotNamed(id2)->pos;
                        edge.LeftTime = 0;
                        my_edges.push_back(edge);
                    }
            }
            for (int j = 0; j < pNames.size(); ++ j) {
                string name = pNames[j];
                if (name.find("LinktimeBuff") != string::npos) {
                    string id2 = name.substr(name.find("LinktimeBuff") + string("LinktimeBuff").size());
                    if (playerInfo.RobotNamed(id2) == nullptr) continue;
                    PPlayerInfo::Edge edge;
                    edge.source = playerInfo.RobotNamed(id1)->pos;
                    edge.target = playerInfo.RobotNamed(id2)->pos;
                    edge.LeftTime = robot["skillList"][name]["remaining"].asInt();
                    my_edges.push_back(edge);
                }
            }
            int max_size = stoi(robot["properties"]["outdeg"].asString());
            if (my_edges.size() > max_size)
                my_edges.resize(max_size);
            playerInfo.edges.insert(playerInfo.edges.begin(), my_edges.begin(), my_edges.end());
        }
        playerInfo.JsonData = jsonToString(root);
        return playerInfo;
    }
    Command translateOperation(const PPlayerInfo& pPlayerInfo, const PCommand::Operation &operation){
        static Command nullCommand;
        Command command;
        command.name = "UseSkill";
        string paramDescription = ParamsDescription[int(operation.type)];
        const PPlayerInfo::Robot* robot = pPlayerInfo.RobotAt(operation.source);
        if (robot == nullptr) throw std::exception();
        command.params.push_back(robot->id);
        command.params.push_back(string(OrderTypeString[int(operation.type)]));
        while (paramDescription.find("<") != string::npos){
            string paramData = paramDescription.substr(paramDescription.find("<") + 1,
                                                       paramDescription.find(">") - paramDescription.find("<") - 1);
            if (paramData == "id"){
                if (pPlayerInfo.RobotAt(operation.target) == NULL) return nullCommand;
                command.params.push_back(pPlayerInfo.RobotAt(operation.target)->id);
            }
            else if (paramData == "x"){
                command.params.push_back(to_string(operation.target.x));
            }
            else if (paramData == "y"){
                command.params.push_back(to_string(operation.target.y));
            }
            else if (paramData == "energy"){
                command.params.push_back(to_string(operation.energy));
            }
            else if (paramData == "type"){
                if (operation.RemainSame)
                    command.params.push_back(to_string(-1));
                else
                    command.params.push_back(to_string(int(operation.targetType)));
            }
            paramDescription = paramDescription.substr(paramDescription.find(">") + 1);
        }
        return command;
    }
    Commands translateCommands(const PPlayerInfo& pPlayerInfo, const PCommand &pCommand){
        Commands commands;
        for (unsigned i = 0; i < pCommand.cmds.size(); ++ i){
            try {
                commands.cmds.push_back(translateOperation(pPlayerInfo, pCommand.cmds[i]));
            }
            catch (std::exception){

            }
        }
        return commands;
    }

    PPlayerInfo interpretPlayerInfo(const PlayerInfo &playerInfo) {
        PPlayerInfo pPlayerInfo = interpretPlayerInfoStatus(stringToJson(playerInfo.status));
        pPlayerInfo.orders = PPlayerInfo::readHumanOrders(playerInfo.push_data);
        return pPlayerInfo;
    }
}